#include <stdlib.h>
#include <string.h>
#include <limits.h>
#include <judgefs.h>
#include <windows.h>

struct diskfs {
	struct judgefs judgefs;
	char *root_dir;
	size_t root_dir_len;
};

struct find_context {
	struct find_context *parent;
	struct find_context *child;
	HANDLE find_file;
	void *ptr_to_free;
};

// the path must be both started and ended with '\0'
static void path_rewrite(char *path)
{
	char *cur = path;
	int state = 0;

	while (1) {
		if (*path == '/')
			*path = '\\';

		if (*path == '\\' || *path == '\0') {
			if (state == 1) {
				cur -= 2;
			} else if (state == 2) {
				cur -= 3;
				while (*cur != '\0')
					if (*--cur == '\\')
						break;
				if (*cur == '\0')
					++cur;
			}
			state = 0;
		} else if (*path == '.') {
			++state;
		} else {
			state = INT_MIN;
		}

		if ((*cur = *path) == '\0')
			break;
		++cur;
		++path;
	}
}

static int path_validate(const char *path)
{
	// this is a table to filter out con, nul, aux, prn, com[0-9] and lpt[0-9]
	// and this is an excellent place to explain where to write comment

	static char table[][5] = {
		{'n', 'a', 'p', 'l', 'c'}, // 0
		{'\0', '\0', '\0', '\0', 'u'}, // 1
		{'\0', '\0', '\0', '\0', 'u'}, // 2
		{'\0', '\0', '\0', '\0', 'r'}, // 3
		{'\0', '\0', '\0', '\0', 'p'}, // 4
		{'\0', '\0', '\0', '\0', 'o'}, // 5
		{'\0', '\0', '\0', '\0', 'l'}, // 6
		{'\0', '\0', '\0', 'x', '\0'}, // 7
		{'\0', '\0', 'n', '\0', '\0'}, // 8
		{'\0', '\0', 't', '\0', '\0'}, // 9
		{'n', 'm', '\0', '\0', '\0'}, // 10
	};

	int state = 0;
	int i;

	while (1) {
		char ch = *path;
		if (ch == '\0' || ch == '/') {
			if (state == 11) {
				return 0;
			} else if (ch == '\0') {
				return 1;
			}
			state = 0;
			goto bed;
		} else if (ch == '.' || ch == ' ') {
			state = 11;
			goto bed;
		} else if (ch >= 'A' && ch <= 'Z') {
			ch += ('a' - 'A');
		}
		if (state >= 0 && state <= 10) {
			for (i = 0; i < 5; ++i) {
				if (ch == table[state][i]) {
					state += (i + 1);
					goto bed;
				}
			}
			state = 13;
		} else if (state == 12 && (ch >= '0' && ch <= '9')) {
			state = 11;
		} else {
			state = 13;
		}
bed:
		++path;
	}
}

static jstatus_t __stdcall open(
	struct judgefs *fs,
	/* out */ struct judgefs_file **file,
	const char *path)
{
	struct diskfs *diskfs = ((struct diskfs *)fs);
	size_t path_len;
	char *real;
	HANDLE os_file;
	
	if (!path_validate(path)) {
		return JSTATUS_NOT_FOUND;
	}
	
	// construct real path
	path_len = strlen(path);
	real = (char *)malloc(diskfs->root_dir_len + 1 + path_len + 1);
	if (!real) {
		return JSTATUS_BAD_ALLOC;
	}
	memcpy(real, diskfs->root_dir, diskfs->root_dir_len);
	real[diskfs->root_dir_len] = '\0';
	memcpy(real + diskfs->root_dir_len + 1, path, path_len + 1);
	path_rewrite(real + diskfs->root_dir_len + 1);
	real[diskfs->root_dir_len] = '\\';

	os_file = CreateFileA(real, GENERIC_READ, FILE_SHARE_READ,
		NULL, OPEN_EXISTING, FILE_ATTRIBUTE_NORMAL, NULL);
	free(real);

	if (os_file == INVALID_HANDLE_VALUE) {
		return JSTATUS_GENERIC_ERROR;
	}

	*file = (struct judgefs_file *)os_file;
	judgefs_add_ref(fs);
	return JSTATUS_SUCCESS;
}

static void __stdcall close(
	struct judgefs *fs,
	struct judgefs_file *file)
{
	HANDLE os_file = (HANDLE)file;
	CloseHandle(os_file);
	judgefs_release(fs);
}

static jstatus_t __stdcall read(
	struct judgefs *fs,
	struct judgefs_file *file,
	char *buffer,
	uint32_t size,
	/* out */ uint32_t *actual_size)
{
	HANDLE os_file = (HANDLE)file;
	DWORD read_size;

	if (!ReadFile(os_file, buffer, size, &read_size, NULL)) {
		return JSTATUS_GENERIC_ERROR;
	}

	*actual_size = (uint32_t)read_size;
	return JSTATUS_SUCCESS;
}

static jstatus_t __stdcall find(
	struct judgefs *fs,
	/* out */ const char **path,
	const char *prefix,
	/* in out */ void **context)
{
	struct diskfs *diskfs = ((struct diskfs *)fs);

	// TODO: not implemented

	return JSTATUS_NOT_IMPLEMENTED;
}

static void __stdcall rundown(
	struct judgefs *fs)
{
	struct diskfs *diskfs = ((struct diskfs *)fs);
	free(diskfs->root_dir);
}

static struct judgefs_vtable vtable = {
	open, close, read, find, rundown,
};

jstatus_t
__stdcall
diskfs_create(
	/* out */ struct judgefs **fs,
	const char *root_dir)
{
	struct judgefs *result;
	struct diskfs *diskfs;
	jstatus_t status;

	status = judgefs_alloc(&result, sizeof(struct diskfs), &vtable);
	if (!JSUCCESS(status)) {
		return status;
	}

	diskfs = (struct diskfs *)result;
	diskfs->root_dir_len = strlen(root_dir);
	diskfs->root_dir = (char *)malloc(diskfs->root_dir_len + 1);
	if (diskfs->root_dir == NULL) {
		judgefs_free(result);
		return JSTATUS_BAD_ALLOC;
	}
	memcpy(diskfs->root_dir, root_dir, diskfs->root_dir_len + 1); 

	*fs = result;
	return JSTATUS_SUCCESS;
}
